﻿#region Using
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
#endregion Using

namespace LloydGame
{
    public class SpotLightShadowMap : ShadowMap
    {
        protected RenderTarget2D m_renderTargetDynamic;
        protected RenderTarget2D m_renderTargetStatic;

        protected GaussianBlurImageProcessor m_gausBlur;
        protected ShadowMapMergeImageProcessor m_imageMerger;
        // constructor
        public SpotLightShadowMap(Game p_game, int p_shadowMapSize) : base(p_game, p_shadowMapSize)
        {
            m_renderTargetDynamic = new RenderTarget2D(
                p_game.GraphicsDevice,
                m_shadowMapSize,
                m_shadowMapSize,
                1,
                SurfaceFormat.Rg32);

            m_renderTargetStatic = new RenderTarget2D(
                p_game.GraphicsDevice,
                m_shadowMapSize,
                m_shadowMapSize,
                1,
                SurfaceFormat.Rg32);

            m_shadowStencilBuffer = new DepthStencilBuffer(
                p_game.GraphicsDevice,
                m_shadowMapSize,
                m_shadowMapSize,
                DepthFormat.Depth24);

            m_gausBlur = new GaussianBlurImageProcessor(p_game, m_shadowMapSize, m_shadowMapSize, SurfaceFormat.Rg32);
            m_imageMerger = new ShadowMapMergeImageProcessor(p_game, m_shadowMapSize, m_shadowMapSize, SurfaceFormat.Rg32);
        }

        #region Shadow Map Creation
        public override void createShadowMap(GameTime p_gameTime, GameLight p_light, bool p_createStatic)
        {
            // temporarily dissable blending
            bool alphaBlendEnableBackup = Game.GraphicsDevice.RenderState.AlphaBlendEnable;
            Game.GraphicsDevice.RenderState.AlphaBlendEnable = false;

            // temporarily enable writing to the depth buffer
            bool depthBufferWriteEnableBackup = Game.GraphicsDevice.RenderState.DepthBufferWriteEnable;
            Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            
            // temporarily replace the stencil buffer
            DepthStencilBuffer stencilBufferBackup = Game.GraphicsDevice.DepthStencilBuffer;
            Game.GraphicsDevice.DepthStencilBuffer = m_shadowStencilBuffer;

            // setup shared parameters
            m_depthMapEffect.Parameters["lightFarClip"].SetValue(p_light.falloffEnd);
            m_depthMapEffect.Parameters["lightPosition"].SetValue(p_light.position);

            // setup view and projection matrices
            Vector3 viewTarget = p_light.position + ((GameSpotLight)p_light).direction;
            Vector3 upVector = ((GameSpotLight)p_light).direction == Vector3.Down ? new Vector3(0.0f, 1.0f, 0.01f) : Vector3.Up;

            float fov = (float)Math.Acos(((GameSpotLight)p_light).cutoffEnd) * 2;

            Matrix view = Matrix.CreateLookAt(p_light.position, viewTarget, upVector);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(fov, 1.0f, 1.0f, p_light.falloffEnd);

            m_viewProj = view * proj;

            if (p_createStatic)
            {
                // set the light's render target
                Game.GraphicsDevice.SetRenderTarget(0, m_renderTargetStatic);

                // let every texel be "in light"
                Game.GraphicsDevice.Clear(Color.White);

                // draw a depth map of the static objects
                drawDepthMap(p_gameTime, true);
                
                // set render target back to the screen
                Game.GraphicsDevice.SetRenderTarget(0, null);

                // save the static depth map
                m_staticShadowMap = m_renderTargetStatic.GetTexture();
            }

            // set the light's render target
            Game.GraphicsDevice.SetRenderTarget(0, m_renderTargetDynamic);

            // let every texel be "in light"
            Game.GraphicsDevice.Clear(Color.White);

            // draw a depth map of the dynamic objects
            drawDepthMap(p_gameTime, false);
            
            // set render target back to the screen
            Game.GraphicsDevice.SetRenderTarget(0, null);

            // save the dynamic depth map
            m_shadowMap = m_renderTargetDynamic.GetTexture();

            // after creating dynamic depth map, merge it with the static one
            m_shadowMap = m_imageMerger.process((Texture2D)m_staticShadowMap, (Texture2D)m_shadowMap, "ShadowMapMerge");

            // blur the combined depth map
            m_shadowMap = m_gausBlur.process((Texture2D)m_shadowMap);

            // restore the graphics device settings
            Game.GraphicsDevice.DepthStencilBuffer = stencilBufferBackup;
            Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = depthBufferWriteEnableBackup;
            Game.GraphicsDevice.RenderState.AlphaBlendEnable = alphaBlendEnableBackup;
        }
        #endregion Shadow Map Creation
    }
}
